package com.ccw.dynamic.impl;

import java.util.ArrayList;
import java.util.List;

import javassist.CannotCompileException;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import javassist.CtField;
import javassist.CtMethod;
import javassist.CtNewConstructor;
import javassist.CtNewMethod;
import javassist.NotFoundException;

import org.apache.commons.lang.StringUtils;

import com.ccw.dynamic.SsistManager;
import com.ccw.string.SuperRegexUtils;

public class SsistManagerImpl implements SsistManager {

	public static final String RE_METHOD_NAME = "superSsist$";

	// private String methodNameAlias = "$[methodName]";

	private List<String> afterCode = new ArrayList<String>();

	private List<String> beforeCode = new ArrayList<String>();

	private List<String> fields = new ArrayList<String>();

	private List<String> importPackages = new ArrayList<String>();

	private List<String> matchRules = new ArrayList<String>();

	private List<SuperMethodInfo> methods = new ArrayList<SuperMethodInfo>();

	private ClassPool pool = ClassPool.getDefault();

	@Override
	public boolean addAfterCode(String code) {
		afterCode.add(code);
		return true;
	}

	@Override
	public boolean addBeforeCode(String code) {
		beforeCode.add(code);
		return true;
	}

	@Override
	public boolean addField(String code) {
		fields.add(code);
		return true;
	}

	private void addFields(CtClass ctClass) throws CannotCompileException {
		// make and add fields
		for (String field : fields) {
			// create field
			CtField f1 = CtField.make(field, ctClass);
			// add field
			ctClass.addField(f1);
		}
	}

	@Override
	public boolean addImportPackage(String code) {
		importPackages.add(code);
		return true;
	}

	@Override
	public void addMatchRule(String matchRule) {
		this.matchRules.add(matchRule);
	}

	@Override
	public boolean addMethod(SuperMethodInfo superMethodInfo) {
		methods.add(superMethodInfo);
		return true;
	}

	private void addMethods(CtClass ctClass) throws NotFoundException,
			CannotCompileException {
		// create method execute with body
		for (SuperMethodInfo superMethodInfo : methods) {
			// build method body
			StringBuilder sb = new StringBuilder();
			for (String line : superMethodInfo.getLines()) {
				if (line.endsWith("\n")) {
					sb.append(line);
				} else {
					sb.append(line + "\n");
				}
			}
			String methodBody = "{" + sb.toString() + "}";

			// get return type
			CtClass returnCtClass = null;
			String returnType = superMethodInfo.getReturnType();
			if (returnType == null || "".equals(returnType)
					|| "void".equals(returnType)) {
				returnCtClass = CtClass.voidType;
			} else {
				returnCtClass = pool.get(returnType);
			}

			// get parameter class
			CtClass[] parameters = null;
			List<CtClass> parClassList = new ArrayList<CtClass>();
			List<SuperParameterInfo> superParameters = superMethodInfo
					.getParameterTypes();
			String[] searchList = new String[superParameters.size()];
			String[] replacementList = new String[superParameters.size()];
			for (int i = 0; i < superParameters.size(); i++) {
				SuperParameterInfo superParameter = superParameters.get(i);
				parClassList.add(pool.get(superParameter.getParameterType()));
				searchList[i] = superParameter.getParameterName();
				replacementList[i] = "$args[" + i + "]";
			}

			if (superParameters.size() > 0) {
				methodBody = StringUtils.replaceEach(methodBody, searchList,
						replacementList);
			}

			if (parClassList.size() > 0) {
				parameters = parClassList.toArray(new CtClass[0]);
			}

			// get exception class
			CtClass[] exceptions = null;
			List<CtClass> excClassList = new ArrayList<CtClass>();
			for (String excType : superMethodInfo.getExceptionTypes()) {
				excClassList.add(pool.get(excType));
			}
			if (excClassList.size() > 0) {
				exceptions = excClassList.toArray(new CtClass[0]);
			}

			String className = ctClass.getName();
			String simpleClassName = className.substring(className
					.lastIndexOf(".") + 1, className.length());
			String methodName = superMethodInfo.getMethodName();
			if (superMethodInfo.getMethodName().equals(simpleClassName)) {
				/*
				 * CtConstructor c1 = CtNewConstructor.make(parameters,
				 * exceptions, methodBody, ctClass); CtConstructor c2 =
				 * CtNewConstructor.make(parameters, exceptions, 2, methodBody,
				 * null, ctClass);
				 */
				/*CtConstructor cc = CtNewConstructor.make(parameters,
						exceptions, methodBody, ctClass);*/
				CtConstructor cc = new CtConstructor(parameters, ctClass);
				cc.setBody(methodBody);
				cc.setModifiers(superMethodInfo.getMethodType());
				ctClass.addConstructor(cc);
			} else {
				CtMethod m1 = CtNewMethod.make(returnCtClass, methodName,
						parameters, exceptions, methodBody, ctClass);
				m1.setModifiers(superMethodInfo.getMethodType());
				// add method execute to class
				ctClass.addMethod(m1);
			}
		}
	}

	private boolean matchMethodName(String methodName) {
		for (String rule : matchRules) {
			if (SuperRegexUtils.regexMatch(rule, methodName)) {
				return true;
			}
		}
		return false;
	}

	private CtClass modifyClass(String targetClass) throws NotFoundException,
			CannotCompileException {
		CtClass ctClass = pool.get(targetClass);
		addFields(ctClass);
		modifyMethod(ctClass);
		addMethods(ctClass);
		return ctClass;
	}

	private CtClass modifyClassByNewMethod(String targetClass)
			throws NotFoundException, CannotCompileException {
		CtClass ctClass = pool.get(targetClass);
		addFields(ctClass);
		modifyMethodByNewMethod(ctClass);
		addMethods(ctClass);
		return ctClass;
	}

	private void modifyMethod(CtClass ctClass) throws CannotCompileException {
		// build before code
		StringBuilder beforeCodes = new StringBuilder();
		for (String line : beforeCode) {
			if (line.endsWith("\n")) {
				beforeCodes.append(line);
			} else {
				beforeCodes.append(line + "\n");
			}
		}
		String sBeforCode = beforeCodes.toString();

		// build after code
		StringBuilder afterCodes = new StringBuilder();
		for (String line : afterCode) {
			if (line.endsWith("\n")) {
				afterCodes.append(line);
			} else {
				afterCodes.append(line + "\n");
			}
		}
		String sAfterCode = afterCodes.toString();

		CtConstructor[] ctConstructors = ctClass.getDeclaredConstructors();
		for (CtConstructor ctConstructor : ctConstructors) {
			if (matchMethodName(ctConstructor.getName())) {
				ctConstructor.insertBefore(sBeforCode);
				ctConstructor.insertAfter(sAfterCode);
				//ctConstructor.setBody("System.out.println(\"this.b=\"+this.b);");
			}
		}

		CtMethod[] ctMethods = ctClass.getDeclaredMethods();
		for (CtMethod ctMethod : ctMethods) {
			if (matchMethodName(ctMethod.getName())) {
				ctMethod.insertBefore(sBeforCode);
				ctMethod.insertAfter(sAfterCode);
			}
		}
	}

	private void modifyMethodByNewMethod(CtClass ctClass)
			throws CannotCompileException, NotFoundException {
		// build before code
		StringBuilder beforeCodes = new StringBuilder();
		for (String line : beforeCode) {
			if (line.endsWith("\n")) {
				beforeCodes.append(line);
			} else {
				beforeCodes.append(line + "\n");
			}
		}
		String sBeforCode = beforeCodes.toString();

		// build after code
		StringBuilder afterCodes = new StringBuilder();
		for (String line : afterCode) {
			if (line.endsWith("\n")) {
				afterCodes.append(line);
			} else {
				afterCodes.append(line + "\n");
			}
		}
		String sAfterCode = afterCodes.toString();

		CtMethod[] ctMethods = ctClass.getDeclaredMethods();
		for (CtMethod ctMethod : ctMethods) {
			if (matchMethodName(ctMethod.getName())) {
				// old method
				String oldMethodName = ctMethod.getName();

				// rename old method
				String reMethodName = RE_METHOD_NAME + oldMethodName;
				ctMethod.setName(reMethodName);

				// build new method name
				String callLine = reMethodName + "($$);\n";
				String newMethodBody = "{" + sBeforCode + callLine + sAfterCode
						+ "}";

				// build new method by name = old method name
				CtMethod newMethod = CtNewMethod.copy(ctMethod, oldMethodName,
						ctClass, null);

				// build alias
				newMethodBody = parseAlias(newMethodBody, newMethod, ctClass);

				newMethod.setBody(newMethodBody);
				ctClass.addMethod(newMethod);
			}
		}
	}

	private CtClass newClass(String targetClass) throws CannotCompileException,
			NotFoundException {
		CtClass ctClass = pool.makeClass(targetClass);
		addFields(ctClass);
		addMethods(ctClass);
		return ctClass;
	}

	private String parseAlias(String body, CtMethod ctMethod, CtClass ctClass) {
		return body.replace("$[methodName]", ctMethod.getName());
	}

	@Override
	public Class<?> process(int type, String targetClass)
			throws NotFoundException, CannotCompileException,
			InstantiationException, IllegalAccessException {
		ClassClassPath classPath = new ClassClassPath(this.getClass());
		pool.insertClassPath(classPath);
		// import package
		for (String importPackage : importPackages) {
			pool.importPackage(importPackage);
		}

		CtClass ctClass = null;
		if (type == MODIFY_CLASS) {
			ctClass = modifyClass(targetClass);
		} else if (type == NEW_CLASS) {
			ctClass = newClass(targetClass);
		} else if (type == MODIFY_CLASS_NEW_METHOD) {
			ctClass = modifyClassByNewMethod(targetClass);
		}

		Class<?> claTargetClass = ctClass.toClass();
		//claTargetClass.newInstance();
		return claTargetClass;
	}

}
